/* * Digital Audio Access Protocol (DAAP) Library * Copyright (C) 2004-2010 Roger Kapsi * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.ardverk.daap.tools; import java.awt.FileDialog; import java.awt.Frame; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import org.ardverk.daap.ByteUtil; import org.ardverk.daap.chunks.Chunk; /** * This tool will help you to detect changes in the DAAP protocol. * * <p> * <ol> * <li>install ethereal * <li>capture the /content-codes request between two iTunes hosts * <li>save the data as foobar.gz * <li>run gzip -d foobar.gz * <li>open foobar with this tool and it will tell you what's new! * </ol> * </p> * * @author Roger Kapsi */ public class ContentCodesAnalyzer { /** Creates a new instance of ContentCodesAnalyzer */ public ContentCodesAnalyzer() { } private static HashMap getContentCodes() throws Exception { HashMap map = new HashMap(); Chunk[] chunks = ChunkUtil.getChunks(); for (int i = 0; i < chunks.length; i++) { Chunk chunk = chunks[i]; String contentCode = chunk.getContentCodeString(); String name = chunk.getName(); int type = chunk.getType(); map.put(contentCode, new ContentCode(contentCode, name, type)); } return map; } private static HashMap readNewChunks(File file) throws IOException { HashMap map = new HashMap(); FileInputStream in = new FileInputStream(file); try { in.skip(0x14); // skip header in.skip(0x04); // skip 'mdcl' byte[] lenBuf = new byte[4]; while (in.read(lenBuf, 0, lenBuf.length) != -1) { int len = ByteUtil.toIntBE(lenBuf, 0); byte[] buf = new byte[len]; if (in.read(buf, 0, buf.length) == -1) break; int pos = 0; pos += 4; // skip 'mcnm' len = ByteUtil.toIntBE(buf, pos); pos += 4; String contentCode = new String(buf, pos, len); pos += len; pos += 4; // skip 'mcna' len = ByteUtil.toIntBE(buf, pos); pos += 4; String name = new String(buf, pos, len); pos += len; pos += 4; // skip 'mcty' len = ByteUtil.toIntBE(buf, pos); pos += 4; int type = ByteUtil.toInt16BE(buf, pos); pos += len; map.put(contentCode, new ContentCode(contentCode, name, type)); in.skip(0x04); // skip 'mdcl' of the next chunk } } finally { in.close(); } return map; } /** * @param args * the command line arguments */ public static void main(String[] args) throws Exception { if (args.length == 0) { FileDialog dialog = new FileDialog(new Frame(), "Select file...", FileDialog.LOAD); dialog.show(); String d = dialog.getDirectory(); String f = dialog.getFile(); if (d != null && f != null) { args = new String[] { d + f }; } else { System.out.println("No file selected... Bye!"); System.exit(0); } } HashMap knownChnunks = getContentCodes(); HashMap newChunks = readNewChunks(new File(args[0])); Iterator it = null; /* * System.out.println("\n+++ KNOWN CHUNKS +++\n"); * * it = knownChnunks.keySet().iterator(); while(it.hasNext()) { * System.out.println(knownChnunks.get(it.next())); } * * System.out.println("\n+++ NEW CHUNKS +++\n"); * * * it = newChunks.keySet().iterator(); while(it.hasNext()) { * System.out.println(newChunks.get(it.next())); } */ List added = new ArrayList(); List removed = new ArrayList(); List changed = new ArrayList(); it = newChunks.keySet().iterator(); while (it.hasNext()) { Object key = it.next(); Object obj = newChunks.get(key); if (knownChnunks.containsKey(key) == false) { added.add(obj); } else { Object obj2 = knownChnunks.get(key); if (obj2.equals(obj) == false) { changed.add(new Object[] { obj, obj2 }); } } } it = knownChnunks.keySet().iterator(); while (it.hasNext()) { Object key = it.next(); Object obj = knownChnunks.get(key); if (newChunks.containsKey(key) == false) { removed.add(obj); } } System.out.println("\n+++ NEW CHUNKS +++\n"); it = added.iterator(); while (it.hasNext()) { System.out.println(it.next()); } System.out.println("\n+++ REMOVED CHUNKS +++\n"); it = removed.iterator(); while (it.hasNext()) { System.out.println(it.next()); } System.out.println("\n+++ CHANGED CHUNKS +++\n"); it = changed.iterator(); while (it.hasNext()) { Object[] obj = (Object[]) it.next(); System.out.println("NEW: " + obj[0]); System.out.println("OLD: " + obj[1]); } /* * FileOutputStream os = new FileOutputStream(new File( * "/Users/roger/foobar.txt")); byte[] dst = new byte[4]; * ByteUtil.toByteBE(SongCodecType.MPEG, dst, 0); os.write(dst, 0, * dst.length); */ } private static final class ContentCode { private String contentCode; private String name; private int type; private ContentCode(String contentCode, String name, int type) { this.contentCode = contentCode; this.name = name; this.type = type; } public boolean equals(Object o) { ContentCode other = (ContentCode) o; return (contentCode.equals(other.contentCode) && name.equals(other.name) && type == other.type); } public String toString() { StringBuffer buf = new StringBuffer(); buf.append("dmap.dictionary = {\n"); buf.append(" dmap.contentcodesnumber = ").append(contentCode) .append("\n"); buf.append(" dmap.contentcodesname = ").append(name) .append("\n"); buf.append(" dmap.contentcodestype = ").append(type) .append("\n"); buf.append("}\n"); return buf.toString(); } } }